package iagovsothello.logger;

import iagovsothello.ai.*;
import iagovsothello.ai.heuristics.*;
import iagovsothello.core.*;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Observer;

public class BasicExperiment extends Experiment {

	private static final long serialVersionUID = -7978131690656733106L;
	private int rounds;
	private int dimension;
	private Log[] logs;
 	private GameState[] games;
	
 	private Agent player1;
 	private Agent player2;
 	
	public BasicExperiment(String name, int dimension, int rounds, Agent player1, Agent player2)
	{
		super(name, 0);
		this.dimension = dimension;
		this.rounds = rounds;
		this.player1 = player1;
		this.player2 = player2;
		init();
	}
	
	public BasicExperiment(String name, boolean print, int dimension, int rounds, Agent player1, Agent player2)
	{
		super(name, 0, print);
		this.dimension = dimension;
		this.rounds = rounds;
		this.player1 = player1;
		this.player2 = player2;
		init();
	}
	
	public BasicExperiment(String name, int rounds, Agent player1, Agent player2)
	{
		super(name,0);
		this.rounds = rounds;
		this.dimension = 8;
		this.player1 = player1;
		this.player2 = player2;
		init();
	}
	
	public void init()
	{
		this.player1.setPlayerNumber(1);
		this.player2.setPlayerNumber(2);
		
		this.logs = new Log[rounds];
		this.games = new GameState[rounds];
		
		GameState startingState = GameState.createStartingState(dimension);
		
		for (int i = 0; i < rounds; i++)
		{
			logs[i] = new Log(player1.clone(), player2.clone());
			games[i] = startingState.clone();
		}
	}

	public static void main(String[] args) 
	{
		TreeSearchAgent a = new HABPruning(1,5,new HeuristicFunctionMobility());
		TreeSearchAgent b = new HABPruning(2,5,new HeuristicFunctionMobility());
		
		BasicExperiment exp = new BasicExperiment("ARGH",8,1,a,b);
//		BasicExperiment exp = new BasicExperiment("ARGH",4,10,new Greedy(1), new ABPruning(2));
		//BasicExperiment exp = new BasicExperiment("ARGH",6,1,new Greedy(1), new HMinimax(2,5,new HeuristicFunctionScore()));
	//	BasicExperiment exp = new BasicExperiment("ARGH",6,1,new Greedy(1), new HMinimax(2,5,new HeuristicFunctionMobility()));
		//BasicExperiment exp = new BasicExperiment("ARGH",6,1,new Greedy(1), new HABPruning(2,5,new HeuristicFunctionScore()));
//		BasicExperiment exp = new BasicExperiment("ARGH",6,10,new HABPruning(2,5,new HeuristicFunctionScore()), new HABPruning(2,5,new HeuristicFunctionCorners()));

		exp.setThreads(0);
		exp.start();
		
		for (Log l : exp.logs)
		{
			System.out.println(l.size());
			Iterator<Entry> it = l.iterator();
			while (it.hasNext())
			{
				Entry e = it.next();
				System.out.println("["+e.turn+"] - "+e.currentPlayer + ": " + e.selectedPosition);
			}
		}
		
//		GameState state = new GameState(4,2);
//		state.setSquareStatus(0, 0, 1);
//		state.setSquareStatus(0, 1, 1);
//		state.setSquareStatus(0, 2, 1);
//		state.setSquareStatus(0, 3, 2);
//		
//		state.setSquareStatus(1, 0, 1);
//		state.setSquareStatus(1, 1, 1);
//		state.setSquareStatus(1, 2, 1);
//		state.setSquareStatus(1, 3, 2);
//		
//		state.setSquareStatus(2, 0, 1);
//		state.setSquareStatus(2, 1, 1);
//		state.setSquareStatus(2, 2, 2);
//		state.setSquareStatus(2, 3, 2);
//		
//		state.setSquareStatus(3, 0, 1);
//		state.setSquareStatus(3, 1, 0);
//		state.setSquareStatus(3, 2, 0);
//		state.setSquareStatus(3, 3, 2);
//		
//		Agent m = new Minimax(2);
//		System.out.println(state.toGridString()+"\n");
//		System.out.println(m.nextMove(state));
	}
	
	public Agent getPlayer1() 
	{
		return player1;
	}

	public Agent getPlayer2() 
	{
		return player2;
	}
	
	public int getDimension()
	{
		return dimension;
	}
	
	public void addObserverRecursive(Observer o)
	{
		addObserver(o);
	}
	
	public int size()
	{
		return rounds;
	}
	
	@Override
	public int totalSize() 
	{
		return rounds;
	}
	
	public Log[] getLogs()
	{
		return logs;
	}
	
	public GameState[] getGames()
	{
		return games;
	}
	
	public long[] getMeanTimes(Agent player)
	{
		int playerNumber = isPlayer(player);
		if (playerNumber == 1 || playerNumber == 2)
			return getMeanTimes(playerNumber);
		else
			return null;
	}
	
	public long[] getMeanTimes(int player)
	{
		int N = (dimension*dimension-4)/2 + (dimension*dimension-4)%2;
		
		long[] times = new long[N];
		
		int[] numbers = new int[N];
		Arrays.fill(numbers, 0);
		
		ArrayList<Iterator<Entry>> iterators = new ArrayList<Iterator<Entry>>(logs.length);
		for (int i = 0; i < logs.length; i++)
		{
			iterators.add(i, logs[i].iterator());
		}
		
		for (int i = 0; i < N; i++)
		{
			times[i] = 0;
 			for (int j = 0; j < logs.length; j++)
			{
				Iterator<Entry> it = iterators.get(j);
				if (it.hasNext())
				{
					Entry e = it.next();
					while (e.currentPlayer != player && it.hasNext())
					{
						e = it.next();
					}
					if (e.currentPlayer == player)
					{
//						if (player == 2)
//						{
//							System.out.println("timeMillis: "+e.timeMillis);
//						}
						times[i] += e.timeMicros;
						numbers[i]++;
					}
				}
			}
 			if (numbers[i] != 0)
 				times[i] = times[i]/numbers[i];
		}
//		System.out.println("\nplayer: "+player);
//		printMeanTimes(times);
		return times;
	}
	
	public int[] getMeanBranchingFactor(Agent player)
	{
		int playerNumber = isPlayer(player);
		if (playerNumber == 1 || playerNumber == 2)
			return getMeanBranchingFactor(playerNumber);
		else
			return null;
	}
	
	public int[] getMeanBranchingFactor(int player)
	{
		int N = (dimension*dimension-4)/2 + (dimension*dimension-4)%2;
		
		int[] bf = new int[N];
		
		int[] numbers = new int[N];
		Arrays.fill(numbers, 0);
		
		ArrayList<Iterator<Entry>> iterators = new ArrayList<Iterator<Entry>>(logs.length);
		for (int i = 0; i < logs.length; i++)
		{
			iterators.add(i, logs[i].iterator());
		}
		
		for (int i = 0; i < N; i++)
		{
			bf[i] = 0;
 			for (int j = 0; j < logs.length; j++)
			{
				Iterator<Entry> it = iterators.get(j);
				if (it.hasNext())
				{
					Entry e = it.next();
					while (e.currentPlayer != player && it.hasNext())
					{
						e = it.next();
					}
					if (e.currentPlayer == player)
					{
						bf[i] += e.branchingFactor;
						numbers[i]++;
					}
				}
			}
 			if (numbers[i] != 0)
 				bf[i] = bf[i]/numbers[i];
		}
//		System.out.println("\nplayer: "+player);
//		printMeanTimes(times);
		return bf;
	}
	
	public long getMaxTime(Agent player)
	{
		int playerNumber = isPlayer(player);
		if (playerNumber == 1 || playerNumber == 2)
			return getMaxTime(playerNumber);
		else
			return -1;
	}
	
	public long getMaxTime(int player)
	{
		long maxTime = Long.MIN_VALUE;
		for (int i = 0; i < logs.length; i++)
		{
			Iterator<Entry> it = logs[i].iterator();
			while (it.hasNext())
			{
				Entry e = it.next();
				while (e.currentPlayer != player && it.hasNext())
				{
					e = it.next();
				}
				if (e.currentPlayer == player && e.timeMicros > maxTime)
				{
					maxTime = e.timeMicros;
				}
			}
		}
		return maxTime;
	}
	
	public int getVictories(Agent player)
	{
		if (player1.equals(player2) && player1.equals(player))
		{
			return rounds-getDraws();
		}
		else if (player1.equals(player))
		{
			return getVictories(1);
		}
		else if (player2.equals(player))
		{
			return getVictories(2);
		}
		else
			return 0;
	}
	
	public int getVictories(int player)
	{
		int victories = 0;
		int otherPlayer;
		if (player == 1)
			otherPlayer = 2;
		else
			otherPlayer = 1;
		
		for (int i = 0; i < rounds; i++)
		{
			if (games[i].getPoints(player) > games[i].getPoints(otherPlayer))
				victories++;
		}
		return victories;
	}
	
	public int getLosses(Agent player)
	{
		if (player1.equals(player2) && player1.equals(player))
		{
			return rounds-getDraws();
		}
		else if (player1.equals(player))
		{
			return getLosses(1);
		}
		else if (player2.equals(player))
		{
			return getLosses(2);
		}
		else
			return 0;
	}
	
	public int getLosses(int player)
	{
		int losses = 0;
		int otherPlayer;
		if (player == 1)
			otherPlayer = 2;
		else
			otherPlayer = 1;
		
		for (int i = 0; i < rounds; i++)
		{
			if (games[i].getPoints(player) < games[i].getPoints(otherPlayer))
				losses++;
		}
		return losses;
	}
	
	public int getDraws()
	{
		int draws = 0;
		for (int i = 0; i < rounds; i++)
		{
			if (games[i].isDraw())
				draws++;
		}
		return draws;
	}

	public long[] getVisitedNodes(Agent player)
	{
		int playerNumber = isPlayer(player);
		if (playerNumber == 1 || playerNumber == 2)
			return getVisitedNodes(playerNumber);
		else
			return null;
	}
	
	public long[] getVisitedNodes(int player)
	{
		int N = (dimension*dimension-4)/2 + (dimension*dimension-4)%2;
		
		long[] nodes = new long[N];
		
		int[] numbers = new int[N];
		Arrays.fill(numbers, 0);
		
		ArrayList<Iterator<Entry>> iterators = new ArrayList<Iterator<Entry>>(logs.length);
		for (int i = 0; i < logs.length; i++)
		{
			iterators.add(i, logs[i].iterator());
		}
		
		for (int i = 0; i < N; i++)
		{
			nodes[i] = 0;
 			for (int j = 0; j < logs.length; j++)
			{
				Iterator<Entry> it = iterators.get(j);
				if (it.hasNext())
				{
					Entry e = it.next();
					while (e.currentPlayer != player && it.hasNext())
					{
						e = it.next();
					}
					if (e.currentPlayer == player)
					{
						nodes[i] += e.nodes;
						numbers[i]++;
					}
				}
			}
 			if (numbers[i] != 0)
 				nodes[i] = nodes[i]/numbers[i];
		}
//		System.out.println("\nplayer: "+player);
//		printMeanTimes(times);
		return nodes;
	}
	
	public int isPlayer(Agent player)
	{
		if (player1.equals(player2) && player1.equals(player))
		{
			return 3;
		}
		else if (player1.equals(player))
		{
			return 1;
		}
		else if (player2.equals(player))
		{
			return 2;
		}
		else
			return 0;
	}
	
	public void printRecursive()
	{
		print();
	}
	
	protected void execute()
	{
		int currentIndex = getAndIncreaseIndex();
		while (currentIndex < rounds)
		{
//			System.out.println("index: "+currentIndex);
			while (!games[currentIndex].isGameOver())
			{
				if (games[currentIndex].getCurrentPlayer() == 1)
					games[currentIndex].move(logs[currentIndex].getPlayer1().nextMove(games[currentIndex]));
				else
					games[currentIndex].move(logs[currentIndex].getPlayer2().nextMove(games[currentIndex]));
			}
			currentIndex = getAndIncreaseIndex();
			setChanged();
			notifyObservers();
		}
	}
	
	protected void end()
	{
		//niente
	}
	
	protected void print()
	{
		int player1Victories = 0;
		int player2Victories = 0;
		int draws = 0;
		int points1, points2;
		float meanPoints1 = 0;
		float meanPoints2 = 0;
		for (int i = 0; i < rounds; i++)
		{
			points1 = games[i].getPoints(1);
			points2 = games[i].getPoints(2);
			meanPoints1 += points1;
			meanPoints2 += points2;
			if (points1 > points2)
				player1Victories++;
			else if (points1 < points2)
				player2Victories++;
			else
				draws++;
			
		}
		
		//calcolo della media
		meanPoints1 = meanPoints1/rounds;
		meanPoints2 = meanPoints2/rounds;
		
		//calcolo della deviazione standard (solo se gamesCount > 1)
		double sdev1 = 0;
		double sdev2 = 0;
		if (rounds > 1)
		{
			for (int i = 0; i < rounds; i++)
			{
				points1 = games[i].getPoints(1);
				points2 = games[i].getPoints(2);
				sdev1 += Math.pow(points1-meanPoints1,2);
				sdev2 += Math.pow(points2-meanPoints2,2);
			}
			sdev1 = Math.sqrt(sdev1/(rounds-1));
			sdev2 = Math.sqrt(sdev2/(rounds-1));
		}
		
		System.out.println("ESPERIMENTO: "+getName());
		System.out.println("Vittorie player1 ("+player1+"): "+player1Victories);
		System.out.println("Vittorie player2 ("+player2+"): "+player2Victories);
		System.out.println("Pareggi: "+draws);
		System.out.println("Stalli giocatore 1: " + getStallCount(1));
		System.out.println("Stalli giocatore 2: " + getStallCount(2));


		System.out.println("Punteggio medio player1: "+meanPoints1);
		if (rounds > 1)
			System.out.println("Dev. standard punteggio player1: "+sdev1);
		System.out.println("Punteggio medio player2: "+meanPoints2);
		if (rounds > 1)
			System.out.println("Dev. standard punteggio player2: "+sdev2);
		System.out.println("\n");
	}
	
	public int getStallCount(int player){
		
		return 0;
	}
}
